home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Demo / metaclasses / Eiffel.py next >
Encoding:
Python Source  |  2000-06-23  |  3.4 KB  |  114 lines

  1. """Support Eiffel-style preconditions and postconditions.
  2.  
  3. For example,
  4.  
  5. class C:
  6.     def m1(self, arg):
  7.         require arg > 0
  8.         return whatever
  9.         ensure Result > arg
  10.  
  11. can be written (clumsily, I agree) as:
  12.  
  13. class C(Eiffel):
  14.     def m1(self, arg):
  15.         return whatever
  16.     def m1_pre(self, arg):
  17.         assert arg > 0
  18.     def m1_post(self, Result, arg):
  19.         assert Result > arg
  20.  
  21. Pre- and post-conditions for a method, being implemented as methods
  22. themselves, are inherited independently from the method.  This gives
  23. much of the same effect of Eiffel, where pre- and post-conditions are
  24. inherited when a method is overridden by a derived class.  However,
  25. when a derived class in Python needs to extend a pre- or
  26. post-condition, it must manually merge the base class' pre- or
  27. post-condition with that defined in the derived class', for example:
  28.  
  29. class D(C):
  30.     def m1(self, arg):
  31.         return whatever**2
  32.     def m1_post(self, Result, arg):
  33.         C.m1_post(self, Result, arg)
  34.         assert Result < 100
  35.  
  36. This gives derived classes more freedom but also more responsibility
  37. than in Eiffel, where the compiler automatically takes care of this.
  38.  
  39. In Eiffel, pre-conditions combine using contravariance, meaning a
  40. derived class can only make a pre-condition weaker; in Python, this is
  41. up to the derived class.  For example, a derived class that takes away
  42. the requirement that arg > 0 could write:
  43.  
  44.     def m1_pre(self, arg):
  45.         pass
  46.  
  47. but one could equally write a derived class that makes a stronger
  48. requirement:
  49.  
  50.     def m1_pre(self, arg):
  51.         require arg > 50
  52.  
  53. It would be easy to modify the classes shown here so that pre- and
  54. post-conditions can be disabled (separately, on a per-class basis).
  55.  
  56. A different design would have the pre- or post-condition testing
  57. functions return true for success and false for failure.  This would
  58. make it possible to implement automatic combination of inherited
  59. and new pre-/post-conditions.  All this is left as an exercise to the
  60. reader.
  61.  
  62. """
  63.  
  64. from Meta import MetaClass, MetaHelper, MetaMethodWrapper
  65.  
  66. class EiffelMethodWrapper(MetaMethodWrapper):
  67.  
  68.     def __init__(self, func, inst):
  69.         MetaMethodWrapper.__init__(self, func, inst)
  70.         # Note that the following causes recursive wrappers around
  71.         # the pre-/post-condition testing methods.  These are harmless
  72.         # but inefficient; to avoid them, the lookup must be done
  73.         # using the class.
  74.         try:
  75.             self.pre = getattr(inst, self.__name__ + "_pre")
  76.         except AttributeError:
  77.             self.pre = None
  78.         try:
  79.             self.post = getattr(inst, self.__name__ + "_post")
  80.         except AttributeError:
  81.             self.post = None
  82.  
  83.     def __call__(self, *args, **kw):
  84.         if self.pre:
  85.             apply(self.pre, args, kw)
  86.         Result = apply(self.func, (self.inst,) + args, kw)
  87.         if self.post:
  88.             apply(self.post, (Result,) + args, kw)
  89.         return Result
  90.     
  91. class EiffelHelper(MetaHelper):
  92.     __methodwrapper__ = EiffelMethodWrapper
  93.  
  94. class EiffelMetaClass(MetaClass):
  95.     __helper__ = EiffelHelper
  96.  
  97. Eiffel = EiffelMetaClass('Eiffel', (), {})
  98.  
  99.  
  100. def _test():
  101.     class C(Eiffel):
  102.         def m1(self, arg):
  103.             return arg+1
  104.         def m1_pre(self, arg):
  105.             assert arg > 0, "precondition for m1 failed"
  106.         def m1_post(self, Result, arg):
  107.             assert Result > arg
  108.     x = C()
  109.     x.m1(12)
  110. ##    x.m1(-1)
  111.  
  112. if __name__ == '__main__':
  113.     _test()
  114.